/**
 * Copyright (C) 2010-2016 eBusiness Information, Excilys Group
 * Copyright (C) 2016-2020 the AndroidAnnotations project
 * <p>
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * Unless required by applicable law or agreed To in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */

package org.ohosannotations.internal.helper;

import org.ohosannotations.internal.exception.ProcessingException;
import org.ohosannotations.logger.Logger;
import org.ohosannotations.logger.LoggerFactory;

import java.io.BufferedReader;
import java.io.CharArrayWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import javax.annotation.processing.ProcessingEnvironment;
import javax.lang.model.element.Element;
import javax.lang.model.util.Elements;

/**
 * 错误的助手
 *
 * @author dev
 * @since 2021-07-22
 */
public class ErrorHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(ErrorHelper.class);
    private static final int CONSTANT_2 = 2;

    /**
     * 得到错误消息
     *
     * @param processingEnv 处理env
     * @param exception 异常
     * @param aaVersion aa版
     * @return {@link String}
     */
    public String getErrorMessage(ProcessingEnvironment processingEnv, ProcessingException exception,
        String aaVersion) {
        String errorMessage = "Unexpected error in OhosAnnotations " + aaVersion + "!" + System.lineSeparator()
            + "You should check if there is already an issue about it on"
            + urlEncodedErrorMessage(exception) + "&type=Issues" + System.lineSeparator()
            + "If none exists, please open an new one with the following "
            + "content and tell us if you can reproduce it or not. "
            + "Don't forget to give us as much information "
            + "as you can (like parts of your code in failure)." + System.lineSeparator();
        errorMessage += "Java version: " + getJavaCompilerVersion() + System.lineSeparator();
        errorMessage += "Javac processors options: "
            + annotationProcessorOptions(processingEnv) + System.lineSeparator();
        errorMessage += "Stacktrace: " + stackTraceToString(exception.getCause());

        Element element = exception.getElement();
        if (element != null) {
            errorMessage += "Thrown from: " + elementContainer(element) + System.lineSeparator();
            errorMessage += "Element (" + element.getClass().getSimpleName() + "): "
                + elementFullString(processingEnv, element) + System.lineSeparator();
        }

        return errorMessage;
    }

    /**
     * 元素完整的字符串
     *
     * @param processingEnv 处理env
     * @param element 元素
     * @return {@link String}
     */
    private String elementFullString(ProcessingEnvironment processingEnv, Element element) {
        Elements elementUtils = processingEnv.getElementUtils();
        CharArrayWriter writer = new CharArrayWriter();
        elementUtils.printElements(writer, element);
        return writer.toString();
    }

    /**
     * 元素的容器
     *
     * @param element 元素
     * @return {@link String}
     */
    private String elementContainer(Element element) {
        Element enclosingElement = element.getEnclosingElement();
        return enclosingElement != null ? enclosingElement.toString() : "";
    }

    /**
     * 注解处理器选项
     *
     * @param processingEnv 处理env
     * @return {@link String}
     */
    private String annotationProcessorOptions(ProcessingEnvironment processingEnv) {
        Map<String, String> options = processingEnv.getOptions();
        Set<Entry<String, String>> optionsEntries = options.entrySet();

        StringBuffer sb = new StringBuffer();
        for (Entry<String, String> optionEntry : optionsEntries) {
            sb.append(optionEntry.getKey())
                .append("=")
                .append(optionEntry.getValue())
                .append(", ");
        }
        String result = sb.toString();
        return result.length() > CONSTANT_2 ? result.substring(0, result.length() - CONSTANT_2) : result;
    }

    /**
     * 获得java编译器版本
     *
     * @return {@link String}
     */
    private String getJavaCompilerVersion() {
        ProcessBuilder pb = new ProcessBuilder("javac", "-version");
        pb.redirectErrorStream(true);

        BufferedReader in = null;
        try {
            Process process = pb.start();
            in = new BufferedReader(new InputStreamReader(process.getInputStream()));
            String buffer = in.readLine();
            process.waitFor();
            return buffer;
        } catch (InterruptedException error) {
            LOGGER.error("getJavaCompilerVersion InterruptedException ", error.getLocalizedMessage());
        } catch (IOException err) {
            LOGGER.error("getJavaCompilerVersion IOException ", err.getLocalizedMessage());
        } finally {
            if (in != null) {
                try {
                    in.close();
                } catch (IOException e) {
                    // cannot help this
                }
            }
        }
        return "unknown";
    }

    /**
     * url编码的错误消息
     *
     * @param es e
     * @return {@link String}
     */
    private String urlEncodedErrorMessage(Throwable es) {
        try {
            return URLEncoder.encode(es.getCause().getClass().getName(), "UTF-8");
        } catch (UnsupportedEncodingException e1) {
            return "";
        }
    }

    /**
     * 堆栈跟踪字符串
     *
     * @param e1 e
     * @return {@link String}
     */
    private String stackTraceToString(Throwable e1) {
        StringWriter writer = new StringWriter();
        PrintWriter pw = new PrintWriter(writer);
        pw.println(e1.getLocalizedMessage());
        return writer.toString();
    }
}
